package networkTraining.epoch;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import networkTraining.IDataSet;
import networkTraining.INetworkTrainer;
import neuralModel.neuron.INeuron;
import neuralModel.neuron.ISignal;
import neuralModel.neuron.RadialNeuron;
import neuralModel.neuron.Signal;
import neuralNetwork.INeuralNetwork;
import neuralNetwork.execution.INeuralNetworkExecutor;
import neuralNetwork.execution.NeuralNetworkExecutor;

public class IncrementalSOMTrainingEpoch extends EpochExecutorBase
{
	private INeuralNetworkExecutor neuralNetworkExecutor;
	private Map<String, Double> localErrorMap;

	private StringBuilder weightInfo;
	private StringBuilder convergenceInfo;
	
	private double sigma0;
	private double eta0;
	private double T1;
	private double T2;
	private int N = 0;
	
	public HashMap<ISignal, Double> oldWeights;
	
	public IncrementalSOMTrainingEpoch(double sigma0, double T1,double T2, double eta0)
	{
		weightInfo = new StringBuilder();
		weightInfo.append("Weight Data\n");
		
		convergenceInfo = new StringBuilder();
		convergenceInfo.append("\n\nConvergence Data\n");
		
		neuralNetworkExecutor = new NeuralNetworkExecutor();
		localErrorMap = new HashMap<String, Double>();
		
		this.sigma0 = sigma0;
		this.T1= T1;
		this.T2= T2;
		this.eta0 = eta0;
		
		oldWeights = new HashMap<ISignal, Double>();
		
	}

	@Override
	public void executeEpoch()
	{
		resetErrors();

		neuralNetworkExecutor.setNeuralNetwork(getNetworkTrainer().getNeuralNetwork());
		INetworkTrainer networkTrainer = getNetworkTrainer();
		INeuralNetwork neuralNetwork = networkTrainer.getNeuralNetwork();
		IDataSet dataSet = networkTrainer.getDataSetProvider().getTrainingData();

		for (int row = 0; row < dataSet.getDataSetSize(); row++)
		{
			// setup input sensors
			for (String name : dataSet.getInputNeuronNames())
			{
				neuralNetwork.getNeuron(name).setOutputValue(dataSet.getInputValueForRow(name, row));
			}

			// run the simulation
			for (int i = 0; i <= neuralNetwork.getLayerCount(); i++)
				neuralNetworkExecutor.performTimeStep();

			
			performLearning(neuralNetwork, ++N);
			
			testConvergence(neuralNetwork);
		}			
		
		
		
		//printWeights(neuralNetwork);
		
		
		
	}
	
	public void performLearning(INeuralNetwork neuralNetwork, int N)
	{
		double min = Double.MAX_VALUE;
		RadialNeuron minNeuron;
		double [] minVector = new double[2];
		
		for(INeuron neuron : neuralNetwork.getLayer(1))
		{
			if(neuron.getOutputValue() < min)
			{
				min = neuron.getOutputValue();
				minNeuron = (RadialNeuron) neuron;
				minVector[0] = minNeuron.getCenter()[0];
				minVector[1] = minNeuron.getCenter()[1];
			}
		}
		
		double sigma = sigma0 * Math.exp(-(N/T1));
		if(Double.isNaN(sigma))
			System.out.println();
		
		double eta = eta0 * Math.exp(-(N/T2));
		
		if(Double.isNaN(eta))
			System.out.println();
		
		
		for(ISignal signal : neuralNetwork.getSignalList())
			oldWeights.put(signal, signal.getWeight());
		
		
		for(INeuron neuron : neuralNetwork.getLayer(1))
		{
			if(neuron instanceof RadialNeuron)
			{
				double[] center = ((RadialNeuron) neuron).getCenter();
				double d2 = Math.pow((minVector[0] - center[0]), 2) + Math.pow((minVector[1] - center[1]), 2);
				
				double H = Math.exp(-(d2/(2*sigma*sigma)));
				
				for(ISignal signal : neuron.getInputSignalList())
				{
					double newWeight = signal.getWeight() + eta*H*(signal.getSignalValue() - signal.getWeight());
					
					if(Double.isNaN(newWeight))
						System.out.println();
					
					signal.setWeight(newWeight);
				}
			}
		}
		
	}
	
	
	public void printWeights(INeuralNetwork neuralNetwork)
	{
		for(INeuron neuron : neuralNetwork.getLayer(1))
		{
			weightInfo.append(String.format("Neruon: %s\t" , neuron.getName()));
			for(ISignal signal : neuron.getInputSignalList())
			{
				weightInfo.append(String.format("%.17f\t" , signal.getWeight()));
			}
			weightInfo.append("\n");
		}
		
		weightInfo.append("\n");
	}
	
	
	public void testConvergence(INeuralNetwork neuralNetwork)
	{
		double sum =0;
		for(ISignal signal : neuralNetwork.getSignalList())
		{
			sum += Math.abs(signal.getWeight() - oldWeights.get(signal));
		}
		
		convergenceInfo.append(sum + "\n");
		
		if(sum < 0.0000000000001)
		{
			printWeights(neuralNetwork);
			System.out.println(weightInfo.toString());
			System.out.println(convergenceInfo.toString());
			System.exit(0);
		}
		
	}
	
}

